﻿using System;
using System.Data;
using System.Data.Odbc;
using System.IO;
using System.Linq;
using System.Text;
using log4net;
using ResxCrunch.Utils;

namespace ResxCrunch.Business.Managers
{
    /// <summary>
    /// Csv handling funtions
    /// </summary>
    public class CsvMngr : Singleton<CsvMngr>
    {
        #region Constants
        /// <summary>
        /// Logger declaration
        /// </summary>
        private static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        #endregion Constants

        #region Attributes
        #endregion Attributes

        #region Public Methods
        /// <summary>
        /// Saves a datatable as a Csv file
        /// </summary>
        /// <param name="table">Table containing data that needs to be saved</param>
        /// <param name="fileNameAndPath">The path to the file containing the file name and path</param>
        /// <param name="separator">The character separating two pieces of data in the same row</param>
        /// <param name="includeHeader">If true, a file header is included</param>
        /// <param name="maxScanRows">The number of max scannable rows in the CSV to decide the types of the columns.</param>
        /// <returns>True on success, else false</returns>
        public bool SaveCsv(DataTable table, string fileNameAndPath, string separator, bool includeHeader, int maxScanRows)
        {
            if (table == null)
            {
                return false;
            }

            try
            {
                // Write contents to csv file
                // ToDo: Experiment with the encoding
                using (StreamWriter sw = new StreamWriter(fileNameAndPath, false, Encoding.Unicode))
                {
                    if (table.Columns.Count > 0)
                    {
                        // Write header if neded
                        if (includeHeader)
                        {
                            StringBuilder sbuilderHeader = new StringBuilder();

                            for (int i = 0; i < table.Columns.Count; i++)
                            {
                                sbuilderHeader.AppendFormat("\"{0}\"{1}", table.Columns[i].ToString().Replace("\"", "\"\""), (i + 1 == table.Columns.Count) ? string.Empty : separator);
                            }

                            sw.WriteLine(sbuilderHeader.ToString());
                        }

                        StringBuilder sb;

                        foreach (DataRow r in table.Rows)
                        {
                            sb = new StringBuilder();

                            for (int j = 0; j < r.ItemArray.Count(); j++)
                            {
                                sb.AppendFormat("\"{0}\"{1}", r.ItemArray[j].ToString().Replace("\"", "\"\""), (j + 1 == r.ItemArray.Count()) ? string.Empty : separator);
                            }

                            sw.WriteLine(sb.ToString());
                        }
                    }
                }

                string baseDir = FileMngr.Instance.ExtractFileDir(fileNameAndPath);
                string fileName = FileMngr.Instance.ExtractFileName(fileNameAndPath);

                if (this.CreateOrUpdateSchemaFile(baseDir, fileName, separator, includeHeader, maxScanRows))
                {
                    log.InfoFormat("Schema.ini file created to '{0}'.", baseDir);
                }
                else
                {
                    log.WarnFormat("Schema.ini file not created to '{0}'.", baseDir);
                }

                log.InfoFormat("CSV '{0}' saved", fileNameAndPath);
                return true;
            }
            catch (Exception ex)
            {
                log.Error("Error saving CSV.", ex);
                return false;
            }
        }

        /// <summary>
        /// Creates a new schema.ini for a csv file or adds the info to an already existing file
        /// </summary>
        /// <param name="schemaPath">The path where the file will be created</param>
        /// <param name="csvFileName">The name of the csv file for the declaration in the schema file</param>
        /// <param name="separator">Separator used in the CSV file to separate two different values in the same line</param>
        /// <param name="includeHeader">If true, a file header is included</param>
        /// <param name="maxScanRows">The number of max scannable rows in the CSV to decide the types of the columns</param>
        /// <returns>True on success, else false</returns>
        public bool CreateOrUpdateSchemaFile(string schemaPath, string csvFileName, string separator, bool includeHeader, int maxScanRows)
        {
            try
            {
                string schemaFullPath = Path.Combine(schemaPath, "schema.ini");
                if (File.Exists(schemaFullPath))
                {
                    bool definitionFound = false;

                    #region Update schema.ini
                    using (StreamReader sr = new StreamReader(schemaFullPath))
                    {
                        string line;
                        string compareValue = string.Concat("[", csvFileName, "]");

                        while (!sr.EndOfStream || definitionFound)
                        {
                            line = sr.ReadLine();

                            if (line.Trim().Equals(compareValue))
                            {
                                definitionFound = true;
                            }
                        }
                    }

                    if (!definitionFound)
                    {
                        using (StreamWriter sw = new StreamWriter(schemaFullPath, true))
                        {
                            this.InsertSchemaInfo(sw, csvFileName, separator, includeHeader, true, maxScanRows);
                        }
                    }
                    #endregion Update schema.ini
                }
                else
                {
                    using (StreamWriter sw = new StreamWriter(schemaFullPath))
                    {
                        this.InsertSchemaInfo(sw, csvFileName, separator, includeHeader, false, maxScanRows);
                    }
                }

                return true;
            }
            catch (Exception ex)
            {
                log.Error("Error while writing schema.ini", ex);
                return false;
            }
        }

        /// <summary>
        /// Reads data from a CSV file to a datatable
        /// ToDo: Create schema if there isn't any
        /// </summary>
        /// <param name="filePath">Path to the CSV file</param>
        /// <returns>
        /// Datatable filled with data read from the CSV file
        /// </returns>
        public DataTable ReadCsv(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                log.Error("Invalid CSV file name.");
                return null;
            }

            try
            {
                DataTable dt = new DataTable("Data");

                string folder = FileMngr.Instance.ExtractFileDir(filePath);
                string fileName = FileMngr.Instance.ExtractFileName(filePath);
                string connectionString = string.Concat(@"Driver={Microsoft Text Driver (*.txt; *.csv)};Dbq=", folder, ";");

                using (OdbcConnection conn = new System.Data.Odbc.OdbcConnection(connectionString))
                {
                    string selectCommand = string.Concat("select * from [", fileName, "]");
                    using (OdbcDataAdapter da = new OdbcDataAdapter(selectCommand, conn))
                    {
                        da.Fill(dt);
                    }
                }

                return dt;
            }
            catch (Exception ex)
            {
                log.Error("Error loading CSV content", ex);
                return null;
            }
        }
        #endregion Public Methods

        #region Private Methods
        /// <summary>
        /// Inserts a declaration into the schema file
        /// </summary>
        /// <param name="sw">The writer pointing to the location of where the declaration will be inserted</param>
        /// <param name="csvFileName">Name of the CSV file the declaration is written for</param>
        /// <param name="separator">Separator used in the CSV file to separate two different values in the same line</param>
        /// <param name="includeHeader">If true, a file header is included</param>
        /// <param name="insertNewLineBefore">If true a new line is inserted before the declaration</param>
        /// <param name="maxScanRows">The number of max scannable rows in the CSV to decide the types of the columns</param>
        private void InsertSchemaInfo(StreamWriter sw, string csvFileName, string separator, bool includeHeader, bool insertNewLineBefore, int maxScanRows)
        {
            if (insertNewLineBefore)
            {
                sw.WriteLine();
            }

            sw.WriteLine(string.Concat("[", csvFileName, "]"));
            sw.WriteLine(string.Concat("Format=Delimited(", separator, ")"));
            sw.WriteLine(string.Concat("ColNameHeader=", includeHeader.ToString()));
            sw.WriteLine(string.Format("MaxScanRows={0}", maxScanRows));
            sw.WriteLine("CharacterSet=UNICODE");
        }
        #endregion Private Methods
    }
}
